package org.multi.core;

import java.util.OptionalInt;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;
import org.multi.core.function.BiIntConsumer;
import org.multi.core.state.BoolState;
import org.multi.core.state.IntState;
import org.multi.core.state.LongState;

@FunctionalInterface
public interface IntMulti {

    static void stop() {
        StopException.stop();
    }

    static IntMulti create(Consumer<IntConsumer> consumer) {
        return consumer::accept;
    }

    static IntMulti range(int start, int end) {
        if (start > end) {
            throw new IllegalArgumentException("start must be less than end");
        }
        return consumer -> {
            for (int item = start; item < end; item++) {
                consumer.accept(item);
            }
        };
    }

    static IntMulti rangeClosed(int start, int end) {
        if (start > end) {
            throw new IllegalArgumentException("start must be less than end");
        }
        return consumer -> {
            for (int item = start; item <= end; item++) {
                consumer.accept(item);
            }
        };
    }

    static IntMulti of(Iterable<Integer> items) {
        return consumer -> {
            for (int item : items) {
                consumer.accept(item);
            }
        };
    }

    static IntMulti of(int... items) {
        return consumer -> {
            for (int item : items) {
                consumer.accept(item);
            }
        };
    }

    static IntMulti of(int t) {
        return consumer -> consumer.accept(t);
    }

    static IntMulti iterate(int seed, IntUnaryOperator next) {
        return consumer -> {
            int item = seed;
            consumer.accept(item);
            while (true) {
                consumer.accept(item = next.applyAsInt(item));
            }
        };
    }

    static IntMulti iterate(int seed1, int seed2, IntBinaryOperator next) {
        return consumer -> {
            int a = seed1;
            int b = seed2;
            consumer.accept(a);
            consumer.accept(b);
            while (true) {
                consumer.accept(b = next.applyAsInt(a, a = b));
            }
        };
    }

    void consume(IntConsumer consumer);

    default void consume(IntConsumer onItem, Consumer<Throwable> onFailure) {
        try {
            consume(onItem);
        } catch (Throwable error) {
            onFailure.accept(error);
        }
    }

    default void consumeOn(IntConsumer consumer) {
        try {
            consume(consumer);
        } catch (StopException ignored) {
        }
    }

    default IntMulti filter(IntPredicate test) {
        return consumer -> consume(item -> {
            if (test.test(item)) {
                consumer.accept(item);
            }
        });
    }

    default IntMulti map(IntUnaryOperator mapper) {
        return consumer -> consume(item -> consumer.accept(mapper.applyAsInt(item)));
    }

    default <R> Multi<R> mapToObj(IntFunction<R> mapper) {
        return consumer -> consume(item -> consumer.accept(mapper.apply(item)));
    }

    default IntMulti flatmap(IntFunction<IntMulti> mapper) {
        return consumer -> consume(item -> mapper.apply(item).consume(consumer));
    }

    default IntMulti mapMulti(BiIntConsumer<IntConsumer> function) {
        return consumer -> consume(item -> function.accept(item, consumer));
    }

    default IntMulti distinct() {
        return boxed().distinct().mapToInt(i -> i);
    }

    default IntMulti sorted() {
        return consumer -> boxed().sort().forEach(consumer::accept);
    }

    default IntMulti peek(IntConsumer action) {
        return consumer -> consume(consumer.andThen(action));
    }

    default IntMulti take(long count) {
        return consumer -> {
            LongState state = new LongState(count);
            consumeOn(item -> {
                consumer.accept(item);
                if (--state.value == 0) {
                    stop();
                }
            });
        };
    }

    default IntMulti takeWhile(IntPredicate test) {
        return consumer -> {
            consumeOn(item -> {
                if (test.test(item)) {
                    consumer.accept(item);
                } else {
                    stop();
                }
            });
        };
    }

    default IntMulti drop(long count) {
        return consumer -> {
            long[] state = {count};
            consume(item -> {
                if (state[0] > 0) {
                    state[0]--;
                } else {
                    consumer.accept(item);
                }
            });
        };
    }

    default IntMulti dropWhile(IntPredicate test) {
        return consumer -> {
            boolean[] state = {true};
            consume(item -> {
                if (state[0] && test.test(item)) {
                    return;
                }
                state[0] = false;
                consumer.accept(item);
            });
        };
    }

    default boolean isEmpty() {
        boolean[] state = {true};
        consumeOn(item -> {
            state[0] = false;
            stop();
        });
        return state[0];
    }

    default void forEach(IntConsumer consumer) {
        consume(consumer);
    }

    default int reduce(int identity, IntBinaryOperator op) {
        IntState state = new IntState(identity);
        consume(item -> state.value = op.applyAsInt(state.value, item));
        return state.value;
    }

    default OptionalInt reduce(IntBinaryOperator op) {
        if (isEmpty()) {
            return OptionalInt.empty();
        }
        IntState state = new IntState(0);
        consume(item -> state.value = op.applyAsInt(state.value, item));
        return OptionalInt.of(state.value);
    }

    default int sum() {
        IntState state = new IntState(0);
        consume(item -> state.value += item);
        return state.value;
    }

    default int min() {
        return reduce(0, Math::min);
    }

    default int max() {
        return reduce(0, Math::max);
    }

    default int count() {
        IntState state = new IntState(0);
        consume(item -> state.value++);
        return state.value;
    }

    default boolean anyMatch(IntPredicate test) {
        BoolState state = new BoolState(false);
        consumeOn(item -> {
            if (test.test(item)) {
                state.value = true;
                stop();
            }
        });
        return state.value;
    }

    default boolean allMatch(IntPredicate test) {
        return !anyMatch(test.negate());
    }

    default boolean noneMatch(IntPredicate test) {
        return !allMatch(test);
    }

    default Multi<Integer> boxed() {
        return consumer -> consume(consumer::accept);
    }

    default void println() {
        consume(System.out::println);
    }
}
